"Advanced Graphics and Data Visualization in R" is brought to you by the Centre for the Analysis of Genome Evolution & Function's (CAGEF) bioinformatics training initiative. This CSB1021 was developed to enhance the skills of students with basic backgrounds in R by focusing on available philosophies, methods, and packages for plotting scientific data. While the datasets and examples used in this course will be centred on SARS-CoV-2 epidemiological and genomic data, the lessons learned herein will be broadly applicable.
This lesson is the third in a 6-part series. The aim for the end of this series is for students to recognize how to import, format, and display data based on their intended message and audience. The format and style of these visualizations will help to identify and convey the key message(s) from their experimental data.
The structure of the class is a code-along style in Jupyter notebooks. At the start of each lecture, skeleton versions of the lecture will be provided for use on the University of Toronto Jupyter Hub so students can program along with the instructor.
Last week we did a deep dive on some of the more popular and broadly applicable visualizations for conveying basic ideas about your data. This week will focus on tidying up your visualizations and adding those extra finishing touches that will help polish them off. Adding, removing, altering graphs. Getting these little details correct help you to avoid alterations with additional software outside of R.
At the end of this lecture you will have covered the following topics
grey background - a package, function, code, command or directory. Backticks are also use for in-line code.
italics - an important term or concept or an individual file or folder
bold - heading or a term that is being defined
blue text - named or unnamed hyperlink
Today's datasets will focus on a number of datasets we've used in our previous lectures.
This data file contains 4 objects:
covid_phu_long.df: COVID-19 daily cases values across Ontario public health units seen in lecture 01. covid_phu_window.df: sliding window data generated from covid_phu_long.df based on a 14-day rolling mean.phu_by_total_cases_desc: a list of Ontario PHUs in descending order by caseloadcovid_demographics_total.df: age group demographics in a long-format that we generated in lecture 02.repr- a package useful for altering some of the attributes of objects related to the R kernel.
tidyverse which has a number of packages including dplyr, tidyr, stringr, forcats and ggplot2
viridis helps to create color-blind palettes for our data visualizations
lubridate and zoo are helper packages used for working with date formats in R
ggthemes, directlabels, ggforce, ggbeeswarm, gghighlight, and ggExtra will provide us new geoms and methods for plotting or altering how our plots look.
ggpubr for arranging our plots.
# None of these packages are already available on JupyterHub
# install.packages("directlabels")
# install.packages("ggbeeswarm", dependencies = TRUE)
# install.packages("gghighlight")
# install.packages("ggExtra")
# install.packages("ggpubr")
# Packages to help tidy our data
library(tidyverse)
# Packages for the graphical analysis section
library(repr)
library(viridis)
# New visualisation packages
library(ggthemes)
library(directlabels)
library(ggforce)
library(ggbeeswarm)
library(gghighlight)
library(ggExtra)
library(ggpubr)
# packages used for working with/formating dates in R
library(lubridate)
library(zoo)
-- Attaching packages --------------------------------------- tidyverse 1.3.0 -- v ggplot2 3.3.2 v purrr 0.3.4 v tibble 3.0.4 v dplyr 1.0.2 v tidyr 1.1.2 v stringr 1.4.0 v readr 1.4.0 v forcats 0.5.0 -- Conflicts ------------------------------------------ tidyverse_conflicts() -- x dplyr::filter() masks stats::filter() x dplyr::lag() masks stats::lag() Loading required package: viridisLite Warning message: "package 'directlabels' was built under R version 4.0.4" Warning message: "package 'ggforce' was built under R version 4.0.4" Warning message: "package 'gghighlight' was built under R version 4.0.4" Attaching package: 'lubridate' The following objects are masked from 'package:base': date, intersect, setdiff, union Attaching package: 'zoo' The following objects are masked from 'package:base': as.Date, as.Date.numeric
Last week in lecture 2 we spent our time highlighting various types of plots and there variants while discerning the proper circumstances of their use. Now that we know which plots to use and when to use them, we can focus on how to clean up your visualizations so they can be their "best self".
Through both lectures and assignments we have already glimpsed at some of the commands and layers we can use to improve upon our graphs whether that is by choosing colour, titles, or legend information. Today we'll explore those options more deeply so you don't have to spend days trying to get your visualizations to look perfect. We'll revisit some old plots and build them up from basics.
Let's start with our PHU caseload data from lecture 1. We'll load it from a .RData file along with some other helpful objects.
# Load some pregenerated data tables for class
# Load Lecture03.RData
load("./data/Lecture03.RData")
ls()
# str(covid_demographics_total.df)
# str(covid_phu_long.df)
# str(phu_window_data.df)
# str(phu_by_total_cases_desc)
head(covid_phu_window.df)
| date | public_health_unit | new_cases | total_phu_new | window_mean |
|---|---|---|---|---|
| <date> | <fct> | <dbl> | <dbl> | <dbl> |
| 2020-03-24 | Algoma | 0 | 0 | 0.5000000 |
| 2020-03-25 | Algoma | 0 | 46 | 0.5000000 |
| 2020-03-26 | Algoma | 0 | 69 | 0.5714286 |
| 2020-03-27 | Algoma | 0 | 124 | 0.5714286 |
| 2020-03-28 | Algoma | 0 | 0 | 0.5714286 |
| 2020-03-29 | Algoma | 0 | 0 | 0.6428571 |
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:5]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# 4. Geoms
geom_line(size=2)
Warning message: "Removed 65 row(s) containing missing values (geom_path)."
From our above plot, we can immediately see that we have issues that need remedying:
aes() assignment used.theme()¶Although we haven't directly discussed themes yet, we have seen it appearing here and there in our individual plots. The influence of themes sets and controls the presentation of titles, labels, text, text, background, legends, etc. You don't directly change the actual information presented in these elements.
Calls to theme() generally take the form of theme(element.component.sub-component = element_*(parameter = value))
Some basic elements include line, rect, text, title, and aspect.ratio. Altering these elements in theme() will alter all elements of their kind (ie all lines, rectangles, text etc.). Alternatively specific element components can be altered more directly. The following table lists most of the possible theme elements and components. They can be as specific as axis.title.x.top. More detailed descriptions can be found here.
| Element | Description | Components | Sub-components | Other |
|---|---|---|---|---|
| axis | x and y axis elements | title, text, ticks, line | x, y, length | top, bottom, left, right |
| legend | all legend elements | background, margin, spacing, key, text, title, position, direction, justification, box | x, y, size, height, width, align, just, spacing | |
| panel | background plotting area | background, border, spacing, grid | x, y, major, minor | |
| plot | entire plot | background, title, subtitle, caption, tax, margin | position | |
| strip | facet labels | background, placement, text, switch | x, y, text, pad | grid, wrap |
You update or set your individual elements using the element_*() functions. Within each element you can typically control aesthetics like fill, colour/color etc. Below is a summary of the elements of concern and their parameters. Specific elements_*() will correspond with the above theme elements.
| element call | description | fill | colour | size | linetype | lineend | arrow | family | face | hjust | vjust | angle | lineheight | margin |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| element_line() | formatting of lines | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | ||||||||
| element_text() | formatting of text | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | ||||
| element_rect() | borders and background | $\checkmark$ | $\checkmark$ | $\checkmark$ | $\checkmark$ | |||||||||
| element_blank() | draws nothing, and assigns no space |
inherit.blank is an additional parameter you can use in these functions. If set to TRUE the existence of a blank element among the parents of this element will cause this element to be blank as well. For example axis.title is the parent of axis.title.x. It's somewhat of a conditional to avoid overriding previous aesthetics assignments that set such elements to element.blank().
legend.position option¶When we are looking to move our legends to different positions, there are 2 areas to consider. The first is the plot space itself which surrounds the data panel (where our data is plotted). The legend.position parameter can take in two types of values. The first is a set of characters: top, bottom, left, and right which relate to the plot area.
Let's start with altering our legend position. It's taking up quite a bit of space on the side. We'll worry about the label issues later but for now, let's move it to the bottom of the plot. At the same time, let's increase our overall text size for the plot.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and data from scratch
covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Theme elements
theme(text = element_text(size=20), # set text size to 20
# Move the legend to the bottom
legend.position = "bottom"
) +
# 4. Geoms
geom_line(size=2, na.rm=TRUE)
Instead of moving the legend to the bottom of our plot, let's use the empty space in the top left corner of the panel instead by accessing the coordinate system ([0:1], [0:1]) that represents the relative positioning of elements within the panel. This system, follows a c(x, y) setup that matches with plotting space with (0,0) representing the lower left corner.
Before we move the legend onto our panel, however, we also have to remember where the legend itself is anchoring when we move it. Are we asking to put the bottom-right corner of the legend into the top-left corner of the plot? Or do we want to match the legend anchor so that the top-left corners are aligned?
Use the legend.justification parameter to properly set this property when moving your legend. It uses the same two-point coordinate concept that we'll use for legend.position.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and data from scratch
covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Theme elements
theme(text = element_text(size=20), # set text size to 20
### 1.1.1.2 Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02, 0.95),
legend.direction = "horizontal"
) +
# 4. Geoms
geom_line(size=2)
Warning message: "Removed 52 row(s) containing missing values (geom_path)."
There are a few more things we can do to the plot for now that include updating the background panel to get rid of the grey colour and maybe darkening our axis tick lines and axis lines themselves.
panel.background parameter which expects an element_rect() to define it's properties.panel.grid.* gives us access to the background axes lines using element_line()axis.* elements to to update their look a bit too.plot a little bit by setting the overally background colour.# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and data from scratch
covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Theme elements
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02,0.95),
legend.direction = "horizontal",
### 1.1.2 Update the panel colour and line colours
panel.background = element_rect("white"),
panel.grid.major = element_line("grey"),
### 1.1.2 Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text(colour="black", face="bold"),
# Update the plot background colour
plot.background = element_rect("lightblue")
) +
# 4. Geoms
geom_line(size=2)
Warning message: "Removed 52 row(s) containing missing values (geom_path)."
ggplot2¶In our above example we made alterations to the theme that affected background colour and axis lines. While some of you may lean on the more artistic side you can also use premade themes from both the ggplot2 package and an additional packages named ggthemes. Below you'll find a list of the themes from ggplot2.
| Theme | Description |
|---|---|
| theme_gray() | Grey background colour, white grid lines. |
| theme_bw() | White background colour, grey grid lines. |
| theme_linedraw() | White background colour, black lines of various widths |
| theme_light() | White background colour, grey lines of various widths |
| theme_dark() | Dark background colour, grey lines of various widths |
| theme_minimal() | No background annotations, grey lines |
| theme_classic() | White background, x/y axis lines, no grid lines |
| theme_void() | A copmletely empty themes, white background, no axis or grid lines |
If you find a theme that you mostly like, you can use that as a base to your graph before making additional theme() alterations. Let's try a few of these out.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and save to an object
phu_window.plot <- covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Theme elements
### 1.2.0 Start with a base theme
theme_minimal() +
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02,0.95),
legend.direction = "horizontal",
# Update the panel to drop the minor y-axis grid lines
panel.grid.minor = element_blank(),
# Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text("black", face="bold"),
) +
# 4. Geoms
geom_line(size=2)
# plot our object to standard output
phu_window.plot
Warning message: "Removed 52 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
# Try to add theme_dark() to our plot. What are the consequences?
phu_window.plot + theme_dark()
Warning message: "Removed 52 row(s) containing missing values (geom_path)."
ggthemes mimics visual styles from multiple sources¶If you are feeling a little more daring with your choices, you can turn to the ggthemes packages to mimic styles from a number of publications such as the Economist, and Wall Street Journal. You can look up a list of the various themes at https://github.com/jrnold/ggthemes.
Like the themes provided by ggplot, you can also make edits to these themes within your scripts.
Two additional package options with different colour palettes and shapes are ggthemr and ggsci.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and data from scratch
covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Theme elements
### 1.3.0 Switch to the stat theme
theme_stata() +
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02,0.95),
legend.direction = "horizontal",
# Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text("black", face="bold"),
) +
# 4. Geoms
geom_line(size=2)
Warning message: "Removed 52 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call.graphics(C_text, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
Now that we have played around with how to reposition legends, and other elements of your plot, we can discuss how to change the actual text content of your plot. Many times we want to relabel axes or legends, even legend labels. There are a number of layers we can work through but we'll present some of the simplest ways to accomplish this.
labs() command¶Up to this point, we've seen the use of different commands to alter the labels and titles like xlab(), ylab(), and ggtitle().
You can also access multiple options within a single command labs() which accepts the following parameters:
...: a list of name-value pairs that map back to an aesthetic (ie x = "X-axis" or colour = "Population")title, subtitle: the title with a subtitle displayed belowcaption: the text for the caption is displayed in the bottom-right by defaulttag: figure text tag/label usually for figure panels in manuscriptsLet's relabel our plot axis and titles to be more accurate. For now we'll drop the Stata theme and go with our own alteration of theme_minimal(). We'll also include a caption in the bottom right to explain how we display the 14-day rolling mean.
Note: a quick way of adding space to your titles, is to include the \n character which produces a carriage return.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and save to an object
phu_window.plot <- covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Theme elements
# Start with a base theme
theme_minimal() +
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02,0.95),
legend.direction = "horizontal",
# Update the panel to drop the minor axis grid lines
panel.grid.minor = element_blank(),
# Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text("black", face="bold"),
) +
### 2.1.0 Add labels to our plot
labs(title = "Mean cases of COVID-19 in a 14-day window across top 5 Ontario Public Health Units\n",
x = "\nWindow date",
y = "Mean cases in 14-day window\n",
colour = "Public Health Unit",
caption = "*14-day rolling mean with date as start of the window") +
# 4. Geoms
geom_line(size=2)
# plot our object to standard output
phu_window.plot
Warning message: "Removed 52 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
labels parameter¶The scale_*() functions can also be used to set the title, limits, breaks, and labels along your axes. Some of these parameters are redundant and can override other ggplot2 layer commands, depending on the order you have included them.
| Parameter | Equivalent ggplot layer command |
|---|---|
| name | xlab(), ylab(), lab(x), lab(y) |
| limits | xlim(), ylim() |
| break | Determine when axis tick marks are generated |
| labels | Rename the labels present at axis tick marks |
For various reasons, you may have categorical or grouped data with unusual names. It may be convenient to code you data this way but letting ggplot2 assign these to your axes or labels may not be suitable. Instead, you can manually rename them using the labels parameter with your various scale_*_discrete() layers. You have to be sure, when manually naming these, to supply a vector with the correct number of arguments to match the number of levels in your categories or groups.
Let's start by relabeling our x-axis show us our dates by month and at the same time we set a limit to show us data starting in July of 2020. We've done this before so it should be easy.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and save to an object
phu_window.plot <- covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Theme elements
# Start with a base theme
theme_minimal() +
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02,0.95),
legend.direction = "horizontal",
# Update the panel to drop the minor axis grid lines
panel.grid.minor = element_blank(),
# Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text("black", face="bold"),
) +
# Add labels to our plot
labs(title = "Mean cases of COVID-19 in a 14-day window across top 5 Ontario Public Health Units\n",
x = "\nWindow date",
y = "Mean cases in 14-day window\n",
colour = "Public Health Unit",
caption = "*14-day rolling mean with date as start of the window") +
# 3. Scaling
### 2.2.1 Start looking at data from July 2020 onwards
scale_x_date(limits = c(as.Date("2020-07-01"), as.Date(max(covid_phu_window.df$date))),
date_breaks = "1 month", date_labels = "%b-%Y") +
# 4. Geoms
geom_line(size=2)
# plot our object to standard output
phu_window.plot
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
limits and breaks¶You can use a similar trick to relabel with continuous data by setting the frequency of tick marks in a scale_*_continuous() layer. There are a number of ways to generate the actual list of tick marks but a character or numeric vector must be assigned to the breaks parameter. You can additionally relabel these tick marks but the vector of labels supplied must match the length of your breaks.
Let's break our y-axis into major tick-marks of every 200 cases by altering scale_y_continuous(). While we're playing with scale, let's update our colour scheme as well.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and save to an object
phu_window.plot <- covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) +
# Start with a base theme
theme_minimal() +
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02,0.95),
legend.direction = "horizontal",
# Update the panel to drop the minor axis grid lines
panel.grid.minor = element_blank(),
# Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text("black", face="bold"),
) +
# Add labels to our plot
labs(title = "Mean cases of COVID-19 in a 14-day window across top 5 Ontario Public Health Units\n",
x = "\nWindow date",
y = "Mean cases in 14-day window\n",
colour = "Public Health Unit",
caption = "*14-day rolling mean with date as start of the window") +
# 3. Scaling
# Start looking at data from July 2020 onwards
scale_x_date(limits = c(as.Date("2020-07-01"), as.Date(max(covid_phu_window.df$date))),
date_breaks = "1 month", date_labels = "%b-%Y") +
### 2.2.2 Change our y-axis breaks
scale_y_continuous(limits=c(0, 1000), breaks = seq(0, 1000, 200)) +
# 4. Geoms
geom_line(size=2)
# plot our object to standard output
phu_window.plot
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
Up to this point, we've danced around the idea of colour in our lectures and assignments. For those of you that aren't familiar with your colour choices, here is a quick breakdown of colour palettes.
A common thing to want to do is to change colours from ggplot2's default rainbow palette. There are many reasons to change a colour palette including
When we are talking about colour palettes and their purpose, there are 3 main types.
Sequential - implies an order to your data - i.e. light to dark implies low values to high values. There are helpful when working with continuous data scales of increasing value e.g. heatmaps.
# Load the RColorBrewer library
library(RColorBrewer)
# display the sequential colour palettes
display.brewer.all(type = "seq")
Diverging - low and high values are extremes, and the middle values are important. This palette will goes from light to dark, middle to outsides with 3 colours mainly used.
# Display the diverging colour palettes
display.brewer.all(type = "div")
Qualitative - there is no quantitative relationship between colours. This is usually used for categorical data when you want each category to be visualized distinctly.
display.brewer.all(type = "qual")
Let's test one of the RColorBrewer palettes out on our data. We'll add it as a layer to phu_window.plot using scale_colour_brewer() to override the colour mappings defined in the aes() layer of the plot. Some parameters we can keep in mind:
type: determines the kind of palette as sequencial (seq), diverging (div) or qualitative (qual)palette: accepts a string name for a palette or an integer that combines with type to pick a paletteNote that colour palettes are not vector recycled when plotting in ggplot. This means if you don't supply enough colours to match your groups, then unassigned groups will simply be cut off or not displayed.
More information on palette order and other parameters can be found here
phu_window.plot + scale_colour_brewer(palette="Dark2")
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
phu_window.plot + scale_colour_brewer(type="qual", palette=6)
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
You can always choose a vector of your own colors using this 'R color cheatsheet' (https://www.nceas.ucsb.edu/~frazier/RSpatialGuides/colorPaletteCheatsheet.pdf).
Names of colours as well as hex colour codes are accepted. You can supply a manual list using the scale_*_manual() command.
# Fill the boxplot using a rep() command
phu_window.plot + scale_colour_manual(values=c("purple", "cornflowerblue", "orange", "#FF0000"))
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
viridis package¶The viridis package also has some nice color palettes (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html). These colour packages are diverging palettes meant to help highlight true colour change across continuous scales. You've seen it come up a few times in our data and these palettes do well for small categorical sets but being to blend as we our categories increase in size.
The main calls we can use follow the format scale_*_viridis_c/d/b() where the "c/d/b" represents continuous/discrete/binned data and the types of additional arguments that can be passed on to augment the call. There are some additional parameters that can be used to set the colours when called:
option: accepts one of 8 possible character representing 5 colour scales; "magma"/"A", "inferno"/"B", "plasma"/"C", "viridis"/"D" or "cividis"/"E".direction: sets the direction of the palette order. Use -1 to reverse it.# Fill the boxplot using a rep() command
phu_window.plot + scale_colour_viridis_d(option="plasma")
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
After preparing your visualization you may considering adding extra annotations. These are usually layers that don't affect the aesthetics or data of your visualization but depending on how you add them and the package you are using this isn't strictly true. For the most part, however, let's consider your annotations as separate from your plot.
annotate() plots with shapes, text, and arrows.¶Sometimes you need to add some additional text, or shapes to your graph that aren't necessarily a part of the data itself. in other words you would like to annotate your plot. To accomplish this you can use the annotate() function which will essentially add geoms to your plot. While these annotations can affect the scale of your plot if required to show your annotation(s), they won't affect the legends nor be treated as actual data - just an overlay to your plot.
The annotate() geom has the following characteristics
| Parameter | Description |
|---|---|
| geom | Can be any number of possible values including "text", "rect", and "segment" |
| xmin, xmax, ymin, ymax, xend, yend | Positioning aesthetics where at least one of these must be defined. |
| ... | Other aesthetics arguments that can be passed along like color = "red" |
| na.rm | If FALSE, missing values are removed with a warning otherwise they are silently removed |
Up to this point we've already added some annotations to this plot in previous lectures. Today we'll try adding a few bits of text and lines segments with arrows instead of boxes.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
# Build our plot and save to an object
phu_window.plot <- covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
filter(public_health_unit %in% phu_by_total_cases_desc[1:4]) %>%
mutate(public_health_unit = factor(public_health_unit),
public_health_unit = fct_reorder(public_health_unit, new_cases, .desc=TRUE)) %>%
# redirect the filtered result to ggplot
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = public_health_unit) +
# Start with a base theme
theme_minimal() +
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
legend.position = c(0.02,0.95),
legend.direction = "horizontal",
# Update the panel to drop the minor axis grid lines
panel.grid.minor = element_blank(),
# Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text("black", face="bold"),
) +
# Add labels to our plot
labs(title = "Mean cases of COVID-19 in a 14-day window across top 5 Ontario Public Health Units\n",
x = "\nWindow date",
y = "Mean cases in 14-day window\n",
colour = "Public Health Unit",
caption = "*14-day rolling mean with date as start of the window") +
# 3. Scaling
# Start looking at data from July 2020 onwards
scale_x_date(limits = c(as.Date("2020-07-01"), as.Date(max(covid_phu_window.df$date)) + 10),
date_breaks = "1 month", date_labels = "%b-%Y") +
# Change our y-axis breaks
scale_y_continuous(limits = c(-10, 1000), breaks = seq(0, 1000, 200)) +
### 3.1.0 Update our line colours
scale_colour_viridis_d(option="plasma") +
# 4. Geoms
geom_line(size=2) +
# 8. Annotations
### 3.1.0 Annotate windows of various milestones
annotate("text", x=as.Date("2020-07-31"), y=500,
label = "Toronto enters stage 3", angle=90, size=10, colour="black") +
annotate("segment", x=as.Date("2020-07-31"), xend=as.Date("2020-07-31"), y = 200, yend = 50,
arrow=arrow(), size=1.5, colour="black") +
# When did vaccinations begin?
annotate("text", x=as.Date("2020-10-30"), y=800,
label = "Vaccinations begin", angle=0, size=10, colour="black") +
annotate("curve", x=as.Date("2020-11-28"), xend=as.Date("2020-12-14"), y = 800, yend = 630,
lineend = "round", linejoin = "round",
arrow=arrow(), size=1.5, colour="black", curvature=-0.5) +
# Last lockdown window
annotate("text", x=as.Date("2020-12-15") + 7, y=950,
label = "Province-wide\nlockdown", angle=0, hjust=1, size=10, colour="red", alpha=0.5) +
annotate("rect", xmin=as.Date("2020-12-26"), xmax=as.Date("2021-02-15"), ymin=-Inf, ymax=Inf,
fill="red", alpha=0.2) +
# The rise of the variants begins Dec 26th too!
annotate("text", x=as.Date("2020-12-01") + 7, y=300,
label = "First report\nof B.1.1.7", angle=0, hjust=1, size=10, colour="blue", alpha=0.8) +
annotate("segment", x=as.Date("2020-12-10"), xend=as.Date("2020-12-26"), y = 300, yend = 0,
lineend = "round", linejoin = "round",
arrow=arrow(), size=1.5, colour="black")
# plot our object to standard output
phu_window.plot
Warning message: "Ignoring unknown parameters: linejoin" Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
Unlike the annotations we just discussed, you may wish to directly label or output information based on your data from the plot. This can be in the form of error bars, or data labels. Sometimes you may want to include your sample size or further highlight your outliers.
directlabels package¶If for some reason you needed to label your plot data directly, the geom_dl() layer from the directlabels packages can be quite useful. The package will replace your colour legends with direct labeling instead since this can (sometimes) be a little cleaner and less confusing. Parameters you should set when working with geom_dl() are:
method: this is the positioning method for the direct label placement and MUST be specified.list() to update additional attributes like fontsize (cex), fontfamily, rotation (rot) etc.aes(): like any geom, you can specify aesthetics information including the labels and colour.Note that adding direct labels this way, however, will not remove the corresponding legend from the plot. It will simply add extra geoms to your plot.
phu_window.plot + geom_dl(method=list("last.qp", cex=2), aes(label=public_health_unit))
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message: "Removed 448 rows containing missing values (geom_dl)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
direct.label() feature¶For simplicity, you can also call on direct.label(), which will automatically remove the associated legend from your plot. You can use it by providing the following parameters:
p: the ggplot object you've already created.method the positioning method as with geom_dl().dl.combine() and include several positioning methods at the same time.list() to update additional attributes like fontsize (cex), fontfamily, rotation (rot) etc.direct.label(phu_window.plot, method=list("last.qp", cex=2))
Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message: "Removed 448 rows containing missing values (geom_dl)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
gghighlight()¶You may find yourself in an instance where you have too many data groups to present (ie 34 PHUs) but would still like the audience to get an overview of your dataset while focusing on a few items. As we have done in the past, you could break groups out using facet_*() but that isn't ideal. We have also filtered for the top PHUs from a previously generated list but then we get no sense of the other PHUs at all.
Instead you can use the gghighlight() layer from the package of the same name. Some helpful parameters from this function include:
...: the expressions you will use to filter data (ie your predicate) which will be passed to dplyr::filter().max_highlight: the maximum number of series to highlight.unhighlighted_params: the aesthetics for your unhighlighted groups.use_group_by: if TRUE, this function will use dplyr::group_by() to evaluate your predicate.use_direct_label: if TRUE, labels will be added directly to the plot instead of using a legend.label_key: the column name for label aesthetics.Let's plot all of our PHU data onto the graph and only highlight the top 4 PHUs as before. We'll have to do some extra fiddling to make it work just right.
# This is going to be a simpler graph so adjust our plot window size accordingly
options(repr.plot.width=20, repr.plot.height=10)
ggh <-
# Build our plot and save to an object
covid_phu_window.df %>%
# Filter for the top 5 infected PHUs
mutate(public_health_unit = factor(public_health_unit),
public_health_unit = fct_reorder(public_health_unit, new_cases, .desc=TRUE))
phu_cases.plot <-
# redirect the filtered result to ggplot
# 1. Data
ggplot(ggh) +
# 2. Aesthetics
aes(x = date, y = window_mean, colour = public_health_unit) +
# Start with a base theme
theme_minimal() +
theme(text = element_text(size=20), # set text size to 20
# Move the legend around to within the panel space
legend.justification = c(0,1),
#legend.position = c(0.02,0.95),
legend.position = "bottom",
legend.direction = "horizontal",
# Update the panel to drop the minor axis grid lines
panel.grid.minor = element_blank(),
# Use a black line for the axes
axis.line = element_line("black"),
axis.text = element_text("black", face="bold"),
) +
# Add labels to our plot
labs(title = "Mean cases of COVID-19 in a 14-day window across top 5 Ontario Public Health Units\n",
x = "\nWindow date",
y = "Mean cases in 14-day window\n",
colour = "Public Health Unit",
caption = "*14-day rolling mean with date as start of the window") +
# 3. Scaling
# Start looking at data from July 2020 onwards
scale_x_date(limits = c(as.Date("2020-07-01"), as.Date(max(covid_phu_window.df$date)) + 10),
date_breaks = "1 month", date_labels = "%b-%Y") +
# Change our y-axis breaks
scale_y_continuous(limits = c(-10, 1000), breaks = seq(0, 1000, 200)) +
# Update our line colours
scale_colour_viridis_d(option="plasma") +
# 4. Geoms
### 3.3.0 Plot all of our public health unit data
geom_line(size=2, aes(x=date, y=window_mean, group = public_health_unit, colour = public_health_unit)) +
### 3.3.0 Highlight just the top 4 PHUs
gghighlight(public_health_unit %in% phu_by_total_cases_desc[1:4]) +
#gghighlight(window_mean > 200, label_key = public_health_unit) +
### 3.3.0 Generate direct labels for the plot
geom_dl(method=list("last.qp", cex=2), aes(label=public_health_unit)) +
# 8. Annotations
# Annotate windows of various milestones
annotate("text", x=as.Date("2020-07-31"), y=500,
label = "Toronto enters stage 3", angle=90, size=10, colour="black") +
annotate("segment", x=as.Date("2020-07-31"), xend=as.Date("2020-07-31"), y = 200, yend = 50,
arrow=arrow(), size=1.5, colour="black") +
# When did vaccinations begin?
annotate("text", x=as.Date("2020-10-30"), y=800,
label = "Vaccinations begin", angle=0, size=10, colour="black") +
annotate("curve", x=as.Date("2020-11-28"), xend=as.Date("2020-12-14"), y = 800, yend = 630,
lineend = "round", linejoin = "round",
arrow=arrow(), size=1.5, colour="black", curvature=-0.5) +
# Last lockdown window
annotate("text", x=as.Date("2020-12-15") + 7, y=950,
label = "Province-wide\nlockdown", angle=0, hjust=1, size=10, colour="red", alpha=0.5) +
annotate("rect", xmin=as.Date("2020-12-26"), xmax=as.Date("2021-02-15"), ymin=-Inf, ymax=Inf,
fill="red", alpha=0.2) +
# The rise of the variants begins Dec 26th too!
annotate("text", x=as.Date("2020-12-01") + 7, y=300,
label = "First report\nof B.1.1.7", angle=0, hjust=1, size=10, colour="blue", alpha=0.8) +
annotate("segment", x=as.Date("2020-12-10"), xend=as.Date("2020-12-26"), y = 300, yend = 0,
lineend = "round", linejoin = "round",
arrow=arrow(), size=1.5, colour="black")
# plot our data
phu_cases.plot
Warning message: "Tried to calculate with group_by(), but the calculation failed. Falling back to ungrouped filter operation..." label_key: public_health_unit Warning message: "Ignoring unknown parameters: linejoin" Warning message: "Removed 3808 row(s) containing missing values (geom_path)." Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message: "Removed 4 rows containing missing values (geom_label_repel)." Warning message: "Removed 448 rows containing missing values (geom_dl)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
geom_*()¶When working with bar or line plots where you may have generated information such as a mean with standard deviation, you can plot that information with geom_errorbar(). Unlike annotations from above this is a specific geom and is treated by the plot like any other geom_*() we've encountered. Under it's aes() argument you can specify the ymin and ymax values or data sources. If you already have pregenerated columns for these values, you can use them directly or you can calculate them on the fly if you have just a mean and standard deviation.
There are alternative formats of the geom_errorbar() as well:
| geom | Description |
|---|---|
| geom_crossbar() | A hollow box with the middle indicated by a horizonal line. |
| geom_errorbarh() | Horizontal versions of the errorbar. |
| geom_linerange() | Draws an interval using a single vertical line. |
| geom_pointrange() | Same as a linerange except an additional point is plotted in the middle of the range. |
Let's recreate one of our plots from lecture 2 using summary data and some of these new geoms!
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
covid_demographics.plot <-
covid_demographics_total.df %>%
# Select for just the important columns
select(public_health_unit, age_group, percent_cases, percent_deaths) %>%
# Pivot the modified table to capture the "stat_group" of percent_cases vs percent_deaths
pivot_longer(cols=c(3,4), names_to = "stat_group", values_to = "percent_PHU_total") %>%
# Group the data both by age group and then stat group
group_by(age_group, stat_group) %>%
# Generate some summary statistics
summarise(mean = mean(percent_PHU_total),
sd = sd(percent_PHU_total),
median = median(percent_PHU_total)) %>%
# Plot the data as a mixture of multiple geoms
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x=age_group, y = mean, linetype=stat_group) +
# Themes
theme(text = element_text(size = 20)) + # set text size
# 3. Scaling
# 4. Data
### 4.1.0 Add an errorbar to represent the standard deviation range
geom_errorbar(width = 0.2, aes(y = mean, ymin = mean-sd, ymax = mean+sd, colour=stat_group), size=1) +
### 4.1.0 Add a point to represent the mean of each error bar
geom_point(aes(y=mean, group = stat_group, shape=age_group), size = 5)
covid_demographics.plot
`summarise()` regrouping output by 'age_group' (override with `.groups` argument) Warning message: "The shape palette can deal with a maximum of 6 discrete values because more than 6 becomes difficult to discriminate; you have 10. Consider specifying shapes manually if you must have them." Warning message: "Removed 8 rows containing missing values (geom_point)."
# Add a line to connect our age groups
covid_demographics.plot + geom_line(aes(x=age_group, y=mean, group = stat_group, colour=stat_group), size=2)
Warning message: "The shape palette can deal with a maximum of 6 discrete values because more than 6 becomes difficult to discriminate; you have 10. Consider specifying shapes manually if you must have them." Warning message: "Removed 8 rows containing missing values (geom_point)."
Now that we've gone and built ourselves an extremely strange plot, (remember, this is just an example) there are a few things we can fix/play with.
guide parameter or guides() layer¶Normally you can let ggplot2 take the wheel and automatically generate guides for you. Whenever you set colour/fill/linetype etc in your aesthetics, this will generate a legend. When the groups are mapped in the same way between different aesthetics, the legends may be combined.
There may be instances, however, when you need to adjust your legend or get rid of it all together. This could range from titles, to combining your guides across different aesthetics commands. There are a number of ways to achieve the same result when working with guides and we'll go through a number of examples. First, however, we should discuss the types of legends:
| guide | short call | Description |
|---|---|---|
| guide_legend() | legend | The base prototype of the legend which integrates how geoms are mapped into values. |
| guide_bins() | bins | A binned version of legends which places ticks between keys and has its own small axis |
| guide_colourbar() | colourbar | For mapping continous colour/fill scales from using scale_fill_*() and scale_colour_*(). |
| guide_coloursteps() | coloursteps | A version of guide_colourbar() except for binned colour and fill scales rather than gradients. |
Within each of the guide types, you can update parameters about text within the legend.
| Component | Sub-components |
|---|---|
| title | name, position, theme, hjust, vjust |
| label | name, position, theme, hjust, vjust |
| key | width, height |
| order | you can determine the order of the guide amongst others using integers [1:99]. 0 sets order by an algorithm |
| other | direction of guide, number of rows/cols |
So where can you use these methods?
Within each scale_*() you declare you can set the parameter guide to one of the above guide types. To exclude a legend for that particular type, set the value to FALSE.
Alternatively, you can use the guides() call to set multiple guides at once using the scale types as parameters ie colour, size, shape.
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
covid_demographics_total.df %>%
# Select for just the important columns
select(public_health_unit, age_group, percent_cases, percent_deaths) %>%
# Pivot the modified table to capture the "stat_group" of percent_cases vs percent_deaths
pivot_longer(cols=c(3,4), names_to = "stat_group", values_to = "percent_PHU_total") %>%
# Group the data both by age group and then stat group
group_by(age_group, stat_group) %>%
# Generate some summary statistics
summarise(mean = mean(percent_PHU_total),
sd = sd(percent_PHU_total),
median = median(percent_PHU_total)) %>%
# Plot the data as a mixture of multiple geoms
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x=age_group, y = mean, linetype=stat_group) +
# Themes
theme(text = element_text(size = 20)) + # set text size
### 4.2.0 Set our other guides
guides(linetype = guide_legend(title="Indicator", order=1),
colour = guide_legend(title="Indicator", order=1)) +
# 3. Scaling
### 4.2.0 rename our x-axis labels
scale_x_discrete(labels=covid_demographics_total.df %>% ungroup() %>% select(age_group) %>%
unique() %>% unlist() %>% as.character %>%
str_replace_all(pattern=" to ", replacement = "-")
) +
### 4.2.0 ggplot only adds 6 shapes automatically so we need to add more manually
scale_shape_manual(values=c(1:nlevels(covid_demographics_total.df$age_group)),
guide=guide_legend(title = "Age group", order = 2)) +
# 4. Data
# Add an errorbar to represent the standard deviation range
geom_errorbar(width = 0.2, aes(y = mean, ymin = mean-sd, ymax = mean+sd, colour=stat_group), size=1) +
# Add a point to represent the mean of each error bar
geom_point(aes(y=mean, group = stat_group, shape=age_group), size = 5) +
# Add a line to connect our age groups
geom_line(aes(x=age_group, y=mean, group = stat_group, colour=stat_group), size=2)
`summarise()` regrouping output by 'age_group' (override with `.groups` argument)
Before we leave the guides() section, we should update our plot one last time. When you are working with so many shapes, sometimes, they can show up a little smaller than you want. You may wish to increase their size on the plot but that may disproportionately increase their size on the legend. You can adjust or hold the key objects to a specific size using the override.aes parameter. We'll be applying this within our guides.
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
covid_demographics_total.df %>%
# Select for just the important columns
select(public_health_unit, age_group, percent_cases, percent_deaths) %>%
# Pivot the modified table to capture the "stat_group" of percent_cases vs percent_deaths
pivot_longer(cols=c(3,4), names_to = "stat_group", values_to = "percent_PHU_total") %>%
# Group the data both by age group and then stat group
group_by(age_group, stat_group) %>%
# Generate some summary statistics
summarise(mean = mean(percent_PHU_total),
sd = sd(percent_PHU_total),
median = median(percent_PHU_total)) %>%
# Plot the data as a mixture of multiple geoms
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x=age_group, y = mean, linetype=stat_group) +
# Themes
theme(text = element_text(size = 20)) + # set text size
# Set our other guides
guides(linetype = guide_legend(title="Indicator", order=1),
colour = guide_legend(title="Indicator", order=1)) +
# 3. Scaling
# relabel our x-axis
scale_x_discrete(labels=covid_demographics_total.df %>% ungroup() %>% select(age_group) %>%
unique() %>% unlist() %>% as.character() %>%
str_replace_all(pattern=" to ", replacement = "-")
) +
### 4.2.1 ggplot only adds 6 shapes automatically so we need to add more manually
scale_shape_manual(values=c(1:nlevels(covid_demographics_total.df$age_group)),
guide=guide_legend(title = "Age group",
order=2,
override.aes = list(size=7, stroke=0.8))) +
# 4. Data
# Add an errorbar to represent the standard deviation range
geom_errorbar(width = 0.2, aes(y = mean, ymin = mean-sd, ymax = mean+sd, colour=stat_group), size=1) +
### 4.2.1 Add a point to represent the mean of each error bar
geom_point(aes(y=mean, group = stat_group, shape=age_group), size = 6, stroke=1.5) +
# Add a line to connect our age groups
geom_line(aes(x=age_group, y=mean, group = stat_group, colour=stat_group), size=2)
`summarise()` regrouping output by 'age_group' (override with `.groups` argument)
ggforce package annotates with simple geom_mark_*() options¶The ggforce() package brings helpful geoms and functions to ggplot2 that can quickly annotate groups of data within your plots. These layers work with ggplot2 like other geom_*() layers so you can add them into your plots quite simply. These objects can also accept aesthetics parameters (including the ability to filter groups) amongst many other theme-esque parameters and are added in an automated fashion. More information can be found here
| geom | Description |
|---|---|
| geom_mark_circle() | Add circles to all of your data groups |
| geom_mark_rect() | Add rounded-corner rectangles to your data groups |
| geom_mark_ellipse() | Add ellipses to all of your data groups |
| geom_mark_hull() | Add a more tightly-fitted shape/blob (aka hull) around your data groups |
You can also add custom shapes, specifying their type, location, etc and extensions to the facet_*() group of layers allow you to facet by different columns, zoom in on part of a graph as a facet, and split facets into multiple plots.
Let's add some ellipses to our plot and exchange our geom_line() for a smoother geom_bspline().
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
covid_demographics_total.df %>%
# Select for just the important columns
select(public_health_unit, age_group, percent_cases, percent_deaths) %>%
# Pivot the modified table to capture the "stat_group" of percent_cases vs percent_deaths
pivot_longer(cols=c(3,4), names_to = "stat_group", values_to = "percent_PHU_total") %>%
# Group the data both by age group and then stat group
group_by(age_group, stat_group) %>%
# Generate some summary statistics
summarise(mean = mean(percent_PHU_total),
sd = sd(percent_PHU_total),
median = median(percent_PHU_total)) %>%
# Plot the data as a mixture of multiple geoms
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x=age_group, y = mean, linetype=stat_group) +
# Themes
theme(text = element_text(size = 20)) + # set text size
# Set our other guides
guides(linetype = guide_legend(title="Indicator", order=1),
colour = guide_legend(title="Indicator", order=1)) +
# 3. Scaling
# relabel our x-axis
scale_x_discrete(labels=covid_demographics_total.df %>% ungroup() %>% select(age_group) %>%
unique() %>% unlist() %>% as.character() %>%
str_replace_all(pattern=" to ", replacement = "-")
) +
# ggplot only adds 6 shapes automatically so we need to add more manually
scale_shape_manual(values=c(1:nlevels(covid_demographics_total.df$age_group)),
guide=guide_legend(title = "Age group",
order=2,
override.aes = list(size=7, stroke = 0.8))) +
# 4. Data
# Add an errorbar to represent the standard deviation range
geom_errorbar(width = 0.2, aes(y = mean, ymin = mean-sd, ymax = mean+sd, colour=stat_group), size=1) +
# Add a point to represent the mean of each error bar
geom_point(aes(y=mean, group = stat_group, shape=age_group), size = 5, stroke = 1.5) +
### 4.3.0 Add ellipses to 2 specific age groups to highlight what we care about
geom_mark_ellipse(aes(group = age_group, filter = age_group %in% c("20 to 29", "90+"), label=age_group),
fill="blue", alpha=0.2) +
### 4.3.0 replace our line with a bezier line that is a little smoother and goes through most of the points
geom_bspline(aes(group = stat_group, colour=stat_group), size=1)
`summarise()` regrouping output by 'age_group' (override with `.groups` argument)
ggbeeswarm takes your plotting your points up to the next level¶As you noticed from last week, we used a couple of different methods for plotting our points onto our boxplots and violin plots. Those geoms are native to the ggplot2 package with some parameters that allow for a more "random" distribution of your data points within a provided area. The goal of the ggbeeswarm package is to generate points that will not overlap but they can also be used to simultaneously simulate the kernel density of your data. There are two geoms supplied that work with the ggplot2 package to accomplish this.
geom_beeswarm() has a number of parameters can be used to set their aes() mappings but also how the points are laid out.
priority: determines the method used to perform the point layout.groupOnX: if TRUE then jitter is added to the x axis (default behaviour) otherwise jitter along the y axis.dodge.width: the amount by which different aesthetics groups (must be a factor) will be dodged.show.legend: determines of the this layer should be included in the legends.geom_quasirandom() works similarly to the beeswarm function with emphasis on additional method of how the points are plotted.
method: determine the method for distributing the points. Options include:varwidth: if TRUE, vary the width of each group based on the number of samples in the group# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
covid_demo_long.df <-
covid_demographics_total.df %>%
# Select for just the important columns
select(public_health_unit, age_group, percent_cases, percent_deaths) %>%
# Pivot the modified table to capture the "stat_group" of percent_cases vs percent_deaths
pivot_longer(cols=c(3,4), names_to = "stat_group", values_to = "percent_PHU_total")
covid_demo_long.df %>%
# Group the data both by age group and then stat group
group_by(age_group, stat_group) %>%
# Generate some summary statistics
summarise(mean = mean(percent_PHU_total),
sd = sd(percent_PHU_total),
median = median(percent_PHU_total)) %>%
# Plot the data as a mixture of multiple geoms
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x=age_group, y = mean, linetype=stat_group) +
# Themes
theme(text = element_text(size = 20)) + # set text size
# Set our other guides
guides(linetype = guide_legend(title="Indicator", order=1),
colour = guide_legend(title="Indicator", order=1)) +
# 3. Scaling
scale_x_discrete(labels=covid_demographics_total.df %>% ungroup() %>% select(age_group) %>%
unique() %>% unlist() %>% as.character() %>%
str_replace_all(pattern=" to ", replacement = "-")
) +
# ggplot only adds 6 shapes automatically so we need to add more manually
scale_shape_manual(values=c(1:nlevels(covid_demographics_total.df$age_group)),
guide=guide_legend(title = "Age group",
order=2,
override.aes = list(size=7, stroke = 0.8))) +
scale_y_continuous(limits=c(-0.1, 0.6)) +
# 4. Data
# Add an errorbar to represent the standard deviation range
geom_errorbar(width = 0.2, aes(y = mean, ymin = mean-sd, ymax = mean+sd, colour=stat_group), size=1) +
# Add a point to represent the mean of each error bar
geom_point(aes(y=mean, group = stat_group, shape=age_group), size = 5, stroke = 1.5) +
# Add ellipses to 2 specific age groups to highlight what we care about
geom_mark_ellipse(aes(group = age_group, filter = age_group %in% c("20 to 29", "90+"), label=age_group),
fill="blue", alpha=0.2) +
# replace our line with a bezier line that is a little smoother and goes through most of the points
geom_bspline(aes(group = stat_group, colour=stat_group), size=1) +
### 4.4.0 Add our points using beeswarm from a _different_ data set
geom_quasirandom(data = covid_demo_long.df, aes(x=age_group, y = percent_PHU_total, group = stat_group),
varwidth=TRUE, method="quasirandom", alpha = 0.5)
`summarise()` regrouping output by 'age_group' (override with `.groups` argument) Warning message: "Removed 3 rows containing missing values (position_quasirandom)."
expression()¶Working in biological science, you will often find yourself wanting to italicize species names or add special characters when name proteins etc. This is not a feat easily accomplished using the options provided by ggplot2. Instead you can generate string objects with the required font-changes or symbols and then provide these to objects to your plot.
There are two routes to accomplish this kind of formatting. We'll explore the first, expression() which makes an expression object. The expression() function interprets a series of strings and characters into a mathematically-formatted expression. When supplied as an argument, this object is interpreted as a mathematical expression and the output formated based on a TeX-like set of rules that parse through the syntax. Within this function, there are a number of parameters that can seem like functions but are implemented within expression() rather than using the base R functions - so don't expect the same kind of behaviours. Here is a non-exhaustive list of potential situations you may encounter.
| Symbol | Description |
|---|---|
| +, -, %*%, %/%, %+-% | basic mathematical symbols for +, -, *, /, and $\pm$ |
| paste(x,y,z), x*y*z | juxtapose x, y, and z without any separators |
| sqrt(x) | square root of x |
| sqrt(x, y) | the yth root of x |
| plain(x), bold(x), italic(x), bolditalic(x), symbol(x), underline() | draw x in normal, bold, italic, bolditalic, symbol and underlined font |
| list(x, y, z) | output a comma-separated list of x, y, z |
| hat(x), tilde(x), dot(x), bar(x) | add symbols above x |
| alpha to omega, Alpha to Omega | Greek symbols in lower and upper case |
| infinity | the infinity symbol |
| x ~ y, x ~~ y | put a space between x and y or put extra space between them |
| phantom(0) | leave a gap for "0" without drawing it |
| frac(x, y), over(x, y) | output x over y |
| atop (x, y) | output x over y without any bar |
Note from above, to build your expressions from multiple parts, you should use the * or paste() operators from within expression().
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
covid_demo_long.df <-
covid_demographics_total.df %>%
# Select for just the important columns
select(public_health_unit, age_group, percent_cases, percent_deaths) %>%
# Pivot the modified table to capture the "stat_group" of percent_cases vs percent_deaths
pivot_longer(cols=c(3,4), names_to = "stat_group", values_to = "percent_PHU_total")
covid_demo_long.df %>%
# Group the data both by age group and then stat group
group_by(age_group, stat_group) %>%
# Generate some summary statistics
summarise(mean = mean(percent_PHU_total),
sd = sd(percent_PHU_total),
median = median(percent_PHU_total)) %>%
# Plot the data as a mixture of multiple geoms
# 1. Data
ggplot(.) +
# 2. Aesthetics
aes(x=age_group, y = mean, linetype=stat_group) +
# Themes
theme(text = element_text(size = 20)) + # set text size
### 4.5.0 title labels
labs(title = expression("Distribution of "~italic("new cases")~" vs "~bold("deaths")~"due to COVID-19 across Ontario PHUs"),
x = "\nAge group",
y = "% representation within PHU\n",
colour = "Public Health Unit",
caption = expression("Errorbars represent mean "%+-%" standard deviation")
) +
# Set our other guides
guides(linetype = guide_legend(title="Indicator", order=1),
colour = guide_legend(title="Indicator", order=1)) +
# 3. Scaling
scale_x_discrete(labels=covid_demographics_total.df %>% ungroup() %>% select(age_group) %>%
unique() %>% unlist() %>% as.character() %>%
str_replace_all(pattern=" to ", replacement = "-")
) +
# ggplot only adds 6 shapes automatically so we need to add more manually
scale_shape_manual(values=c(1:nlevels(covid_demographics_total.df$age_group)),
guide=guide_legend(title = "Age group",
order=2,
override.aes = list(size=7, stroke = 0.8))) +
scale_y_continuous(limits=c(-0.1, 0.6)) +
# 4. Data
# Add an errorbar to represent the standard deviation range
geom_errorbar(width = 0.2, aes(y = mean, ymin = mean-sd, ymax = mean+sd, colour=stat_group), size=1) +
# Add a point to represent the mean of each error bar
geom_point(aes(y=mean, group = stat_group, shape=age_group), size = 5, stroke = 1.5) +
# Add ellipses to 2 specific age groups to highlight what we care about
geom_mark_ellipse(aes(group = age_group, filter = age_group %in% c("20 to 29", "90+"), label=age_group),
fill="blue", alpha=0.2) +
# replace our line with a bezier line that is a little smoother and goes through most of the points
geom_bspline(aes(group = stat_group, colour=stat_group), size=1) +
# Add our points using beeswarm from a _different_ data set
geom_quasirandom(data = covid_demo_long.df, aes(x=age_group, y = percent_PHU_total, group = stat_group),
varwidth=TRUE, method="quasirandom", alpha = 0.5)
`summarise()` regrouping output by 'age_group' (override with `.groups` argument) Warning message: "Removed 3 rows containing missing values (position_quasirandom)."
bquote()¶Unlike the expression() function, using bquote() allows you to reference information which may be stored in variables so that you can add these instead of explicitly including the words you want. When thinking about using bquote() you can break your math notation into four forms of syntax. These sections or forms can be joined with the ~ symbol.
| Class of text | Syntax | Description |
|---|---|---|
| Strings | "my text" ~ | Words and non-mathematical text that you want to print as-is |
| Math Expressions | infinity, alpha, frac(x, y) | Unquoted and essentially the same kinds of symbols useable by ?plotmath and expression(). |
| Numbers | 1, 42, 900000 | Use unquoted when part of math notation. |
| Variables | .(variable) | Used to pass in a string or numeric into your equation. |
Many R-enthusiasts prefer this form of generating expressions for it's flexibility to build whatever you want.
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
covid_demo_long.df <-
covid_demographics_total.df %>%
# Select for just the important columns
select(public_health_unit, age_group, percent_cases, percent_deaths) %>%
# Pivot the modified table to capture the "stat_group" of percent_cases vs percent_deaths
pivot_longer(cols=c(3,4), names_to = "stat_group", values_to = "percent_PHU_total")
summary.df <-
covid_demo_long.df %>%
# Group the data both by age group and then stat group
group_by(age_group, stat_group) %>%
# Generate some summary statistics
summarise(mean = mean(percent_PHU_total),
sd = sd(percent_PHU_total),
median = median(percent_PHU_total),
count = n())
sample.min = min(summary.df$count)
# Plot the data as a mixture of multiple geoms
phu_age.plot <-
# 1. Data
ggplot(summary.df) +
# 2. Aesthetics
aes(x=age_group, y = mean, linetype=stat_group) +
# Themes
theme(text = element_text(size = 20)) + # set text size
### 4.6.0 title labels
labs(title = expression("Distribution of"~italic("new cases")~
" vs "~bold("deaths")~"due to COVID-19 across Ontario PHUs"),
x = "\nAge group",
y = "% representation within PHU\n",
colour = "Public Health Unit",
caption = bquote("Errorbars represent mean "~ ''%+-%'' ~"standard deviation with n"~''>=''~.(sample.min))
) +
# Set our other guides
guides(linetype = guide_legend(title="Indicator", order=1),
colour = guide_legend(title="Indicator", order=1)) +
# 3. Scaling
scale_x_discrete(labels=covid_demographics_total.df %>% ungroup() %>% select(age_group) %>%
unique() %>% unlist() %>% as.character() %>%
str_replace_all(pattern=" to ", replacement = "-")
) +
# ggplot only adds 6 shapes automatically so we need to add more manually
scale_shape_manual(values=c(1:nlevels(covid_demographics_total.df$age_group)),
guide=guide_legend(title = "Age group",
order=2,
override.aes = list(size=7, stroke = 0.8))) +
scale_y_continuous(limits=c(-0.1, 0.6)) +
# 4. Data
# Add an errorbar to represent the standard deviation range
geom_errorbar(width = 0.2, aes(y = mean, ymin = mean-sd, ymax = mean+sd, colour=stat_group), size=1) +
# Add a point to represent the mean of each error bar
geom_point(aes(y=mean, group = stat_group, shape=age_group), size = 5, stroke = 1.5) +
# Add ellipses to 2 specific age groups to highlight what we care about
geom_mark_ellipse(aes(group = age_group, filter = age_group %in% c("20 to 29", "90+"), label=age_group),
fill="blue", alpha=0.2) +
# replace our line with a bezier line that is a little smoother and goes through most of the points
geom_bspline(aes(group = stat_group, colour=stat_group), size=1) +
# Add our points using beeswarm from a _different_ data set
geom_quasirandom(data = covid_demo_long.df, aes(x=age_group, y = percent_PHU_total, group = stat_group),
varwidth=TRUE, method="quasirandom", alpha = 0.5)
# plot our graph
phu_age.plot
`summarise()` regrouping output by 'age_group' (override with `.groups` argument) Warning message: "Removed 3 rows containing missing values (position_quasirandom)."
ggExtra¶Marginal plots are a very specialized plot type from the ggExtra package which combines scatterplot data with distribution data in the margins. The main plot panel has your two variables along the x and y axis. Secondary plots are made on the opposite margins and can be in the form of distribution-based object ie., histograms, boxplots, etc.
The workhorse of this package is the ggMarginal() function which takes as input paramaters:
p: the ggplot object you would like to add todata: optional as the information can be drawn from p, otherwise it can be a data.frame object of other datax: the variable name along the x-axisy: the variable name along the y-axistype: the type of marginal plot to show margins: along which margins to show the plotsxparams, yparams: extra paramaters to use only for the x or y marginal plotsgroupColour, groupFill: if TRUE, the colour or fill of the marginal plots will be mapped to the aesthetics of the scatterplotLet's re-imagine our PHU age group data now as a scatterplot with marginal boxplots. While this won't be the clearest visualization of this kind of data it will help to demonstrate how to generate marginal plots with your data.
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=10)
# Build our marginal plot from the wider-format that data we have
phu_age_scatter.plot <-
# 1. Data
ggplot(covid_demographics_total.df) +
# 2. Aesthetics
aes(x=percent_cases, y = percent_deaths, colour = age_group) +
# Themes
theme_grey() +
theme(text = element_text(size = 20), # set text size
legend.position = "bottom" # Move our legend to the bottom
) +
# Update the legend so that the legend keys are larger
guides(colour=guide_legend(override.aes= list(size=4))) +
# Scaling
scale_colour_viridis_d(option = "viridis") +
# 4. Geoms
geom_point(size = 4, alpha = 0.8) # Add our data points
#phu_age_scatter.plot
# Add our marginal boxplots to our graph
phu_marginal.plot <- ggMarginal(phu_age_scatter.plot, type="boxplot", groupFill = TRUE, margin="both")
# plot our marginal plot
phu_marginal.plot
There are many fantastic R packages to analyze and visualize your data. As a group, we are likely working in a variety of specialized areas. The plots we have made so far today should be useful for data exploration for many different kinds of data. In this final section we are going to learn how to arrange multiple plots per page for those publication-ready figures.
ggarrange()¶There are a variety of methods to mix multiple graphs on the same page, however ggplot2 does not work well with all of them. I am going to work with a package base that uses gridExtra(which allows us to arrange plots) but works well with ggplot2 called ggpubr (which allows us to align the axes of our plots). For a demonstration, we are going to take 3 plots that we made earlier (a boxplot, a histogram, and a dot plot), save them as objects, and then arrange and align them in the same figure. (http://www.sthda.com/english/rpkgs/ggpubr/)
ggarrange() is a function that takes your plots, their labels, and how you would like your plots arranged in rows and columns. To start let's put our PHU case data (phu_cases.plot) above our PHU age group data (phu_age.plot). If you picture each plot as a square in a grid, we need one column (one for each plot, ncol = 1) and two rows (since they are stacked, nrow = 2).
# Load the ggpubr package
#library(ggpubr)
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=20)
# Arrange the two plots in a single page
ggarrange(phu_cases.plot, phu_age.plot,
labels = c("A", "B"),
ncol = 1, nrow = 2)
Warning message: "Removed 3808 row(s) containing missing values (geom_path)." Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message: "Removed 4 rows containing missing values (geom_label_repel)." Warning message: "Removed 448 rows containing missing values (geom_dl)." Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message in grid.Call(C_stringMetric, as.graphicsAnnot(x$label)): "font family 'black' not found in PostScript font database" Warning message: "Removed 3 rows containing missing values (position_quasirandom)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
Next we will add in the boxplot by nesting a ggarrange() call within another.
Imagine a square with 4 boxes.
To do this, we are arranging 2 rows (one with the line graph and one with the [age group + marginal plot], nrow = 2) and we are arranging 2 columns in the bottom row (one with the age group and one with the marginal plot, ncol = 2).
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=20)
# Arrange the two plots in a single page
ggarrange(phu_cases.plot, # row 1 plot
# row 2 plots
ggarrange(phu_age.plot, phu_marginal.plot,
labels = c("B", "C"),
ncol = 2,
nrow = 1
),
# finish specifying characteristics of the two-row arrangement
labels = c("A"),
ncol = 1,
nrow = 2
)
Warning message: "Removed 3 rows containing missing values (position_quasirandom)." Warning message: "Removed 3808 row(s) containing missing values (geom_path)." Warning message: "Removed 448 row(s) containing missing values (geom_path)." Warning message: "Removed 4 rows containing missing values (geom_label_repel)." Warning message: "Removed 448 rows containing missing values (geom_dl)." Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database" Warning message in grid.Call(C_textBounds, as.graphicsAnnot(x$label), x$x, x$y, : "font family not found in Windows font database"
align and font()¶Okay, there are a few problems with this arrangement. Spacing aside, our title in plot B has spread over into area C. If you wanted to keep it, you would have to fix up the text in the plot and try again. However, we can treat the plots much like their own data and keep altering them with the + symbol. That mean for a quick fix, we could just remove the title altogether. Do you remember how to access the plot title?
Problem 2: the x-axes in our B/C plots don't line up well. Would it look better if they did? If y-axis lines or x-axis lines are not aligned, this can be fixed with a call to align = "v" or align="h".
Problem 3: the font labels denoting each plot look a little small overall. We can change this aspect with the font.labels parameter.
If you wanted to make sure all axis titles are the same size you can specify these small changes using font(). You can try to access these attributes through simple names like "axis.title", and "legend.title" ie font("axis.title", size=9) but you need to set each graph and each attribute separately.
Let's drop our plot B title, and try to shore up the axes between B and C. Unfortunately we may be stopped by the crowded spacing at the bottom of these plots.
# Adjust our plot window size according to the expected output
options(repr.plot.width=20, repr.plot.height=20)
# Arrange the two plots in a single page
ggarrange(phu_cases.plot,
ggarrange(phu_age.plot + ..., # remove the title
phu_marginal.plot,
labels = c("B", "C"),
ncol = 2,
nrow = 1,
... = list(size=20), # make the labels larger
align = "h" # Try to align the x-axis of both plots
),
labels = c("A"),
ncol = 1,
nrow = 2,
... = list(size=20) # Match the increased label size of the other plots
)
Today we have dug deep into altering and playing with our plots to help get them to that extra level. Although there is far more to explore, this should cover most of your needs when it comes to cleaning up your plots. Looking a little bit ahead at this week's assignment, you will look at canada-wide vaccination data.
You now have the tools to create plots like this:
This week's assignment will be found under the current lecture folder under the "assignment" subfolder. It will include a Jupyter notebook that you will use to produce the code and answers for this week's assignment. Please provide answers in markdown or code cells that immediately follow each question section.
| Assignment breakdown | ||
|---|---|---|
| Code | 50% | - Does it follow best practices? |
| - Does it make good use of available packages? | ||
| - Was data prepared properly | ||
| Answers and Output | 50% | - Is output based on the correct dataset? |
| - Are groupings appropriate | ||
| - Are correct titles/axes/legends correct? | ||
| - Is interpretation of the graphs correct? |
Since coding styles and solutions can differ, students are encouraged to use best practices. Assignments may be rewarded for well-coded or elegant solutions.
You can save and download the Jupyter notebook in its native format. Submit this file to the the appropriate assignment section by 12pm on the date of our next class: March 25th, 2021.
The R Graph Gallery: https://www.r-graph-gallery.com/index.html
Advanced examples of direct labeling with geom_dl(): https://directlabels.r-forge.r-project.org/examples.html
More information about the gghighlight package: https://cran.r-project.org/web/packages/gghighlight/vignettes/gghighlight.html
Using expression(): https://stat.ethz.ch/R-manual/R-devel/library/grDevices/html/plotmath.html
Using bquote(): https://www.r-bloggers.com/2018/03/math-notation-for-r-plot-titles-expression-and-bquote/
More options for ggarrange(): https://rpkgs.datanovia.com/ggpubr/reference/ggarrange.html